package georah.map
{

    import flash.display.Shape;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.utils.Dictionary;
    import flash.utils.getDefinitionByName;
    import flash.utils.getQualifiedClassName;
    
    import georah.carto.Extent;
    import georah.carto.ExtentZ;
    import georah.carto.GISCamera;
    import georah.carto.IGISClient;
    import georah.carto.IGISClientInfo;
    import georah.carto.PanZoom;
    import georah.carto.SRS;
    import georah.carto.geometries.OGCPoint;
    import georah.map.layers.MapLayer;
    import georah.map.tools.MapTool;
    
    import mx.core.UIComponent;

    /**
     *
     * @author yuanm
     */
    public class Map extends UIComponent implements IGISClient, IGISClientInfo
    {
        private static const PHASE_PROGRESS:int = 3;

        private static const PHASE_START:int = 1;

        private static const PHASE_STOP:int = 4;

        private static const PHASE_UPDATE:int = 2;

        private static const PHASE_WAIT:int = 0;
        
        private var _LODs:Array=[];

        private var _acetate:MapLayer = new MapLayer();

        private var _camera:PanZoom = null;

        private var _cameraDefault:PanZoom = null;
        
        private var _cameraExtent:ExtentZ=null;

        private var _camera_changed:Boolean = false;

        private var _dragPhase:int = PHASE_WAIT;

        private var _lastCenterX:Number = NaN;

        private var _lastCenterY:Number = NaN;

        private var _lastHeight:Number = NaN;

        private var _lastScale:Number = NaN;

        private var _lastWidth:Number = NaN;

        private var _layers:UIComponent = new UIComponent();

        private var _mask:Shape = new Shape();

        private var _ready:Boolean = false;

        private var _size_changed:Boolean = false;

        private var _tools:Dictionary = new Dictionary(true);
        
        public final function get LODs():Array
        {
        	return _LODs;
        }
        
        public final function set LODs(value:Array):void
        {
        	_LODs=value;
        }

        public final function get acetate():MapLayer
        {
            return _acetate;
        }

        public final function addLayer(layer:MapLayer):void
        {
            layer.open(this);
            _layers.addChild(layer);
            _layers.addChild(_acetate);
        }

        public final function get camera():GISCamera
        {
            return _camera;
        }

        public final function set camera(value:GISCamera):void
        {
            if (_camera)
            {
                _lastCenterX = _camera.center.x;
                _lastCenterY = _camera.center.y;
                _lastScale = _camera.scale;
            }
            else if (!_cameraDefault)
            {
                _cameraDefault = PanZoom(value);
            }
            _camera = PanZoom(value);
            _camera_changed = true;
            invalidateProperties();
        }

        public final function get cameraDefault():GISCamera
        {
            return _cameraDefault;
        }

        public final function set cameraDefault(value:GISCamera):void
        {
            _cameraDefault = PanZoom(value);
        }
        
        public final function get cameraExtent():ExtentZ
        {
        	return _cameraExtent;
        }
        
        public final function set cameraExtent(value:ExtentZ):void
        {
        	_cameraExtent = value;
        }

        public final function clearDisabledTools():void
        {
            for (var htype:*in _tools)
            {
                if (!_tools[htype].ready)
                {
                    delete _tools[htype];
                }
            }
        }

        public final function clearTools():void
        {
            for (var htype:*in _tools)
            {
                delete _tools[htype];
            }
        }

        public final function disableTool(toolType:Class):void
        {
            var handler:MapTool = _tools[toolType];
            if (handler)
            {
                handler.disable();
            }
        }

        public final function enableTool(tool:MapTool):void
        {
            var moduleClass:Class = getDefinitionByName(getQualifiedClassName(tool)) as Class;
            if (!_tools[moduleClass])
            {
                _tools[moduleClass] = tool;
            }
            tool.enable(this);
            for each (var h:MapTool in _tools)
            {
                if ((tool != h && tool.keyboardLocked && h.keyboardLocked) || (tool != h && tool.mouseLocked && h.mouseLocked))
                {
                    h.disable();
                }
            }
        }

        public final function get envelope():Extent
        {
            return _camera.calcBBox(width, height);
        }

        public final function fromPixelToWhere(pixel:Point):OGCPoint
        {
            return SRS.fromPixelToWhere(pixel, this);
        }

        public final function fromWhereToLayersPixel(where:OGCPoint):Point
        {
            var pixel:Point = SRS.fromWhereToPixel(where, this);
            return new Point(pixel.x - layers.x, pixel.y - layers.y);
        }

        public final function fromWhereToPixel(where:OGCPoint):Point
        {
            return SRS.fromWhereToPixel(where, this);
        }

        public final function getTool(toolType:Class):MapTool
        {
            return _tools[toolType];
        }

        override public function set height(value:Number):void
        {
            _lastHeight = super.height;
            super.height = value;
            _size_changed = true;
            invalidateProperties();
        }

        public final function get layers():UIComponent
        {
            return _layers;
        }

        public final function get ready():Boolean
        {
            return _ready;
        }

        public final function removeLayer(layer:MapLayer):void
        {
            _layers.removeChild(layer);
            layer.close();
        }

        public final function get value():MapValue
        {
            return new MapValue(width, height, camera.where.SRID, camera.where.x, camera.where.y, PanZoom(camera).scale);
        }

        public final function set value(value:MapValue):void
        {
            width = value.width;
            height = value.height;
            camera = new PanZoom(value.SRID, value.centerX, value.centerY, value.scale);
        }

        override public function set width(value:Number):void
        {
            _lastWidth = super.width;
            super.width = value;
            _size_changed = true;
            invalidateProperties();
        }

        override protected function commitProperties():void
        {
            var lastMap:MapValue = new MapValue(_lastWidth, _lastHeight, camera.where.SRID, _lastCenterX, _lastCenterY, _lastScale);

            if (_size_changed)
            {
                dispatchEvent(new MapEvent(MapEvent.SIZE_CHANGE, value, lastMap));
            }

            if (_camera_changed)
            {
                dispatchEvent(new MapEvent(MapEvent.CAMERA_CHANGE, value, lastMap));
            }

            if (_size_changed || _camera_changed)
            {
                dispatchEvent(new MapEvent(MapEvent.CHANGE, value, lastMap));
                _size_changed = false;
                _camera_changed = false;
            }
        }

        override protected function createChildren():void
        {
            addChild(_mask);
            addChild(_layers);
            addLayer(_acetate);
            mask = _mask;
            _ready = true;

            addEventListener(MouseEvent.CLICK, clickHandler);
            addEventListener(MouseEvent.DOUBLE_CLICK, doubleClickHandler);
            addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
            addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
            addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
            addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);

            dispatchEvent(new MapEvent(MapEvent.READY, value, null));
        }

        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            graphics.beginFill(0xffffff);
            graphics.drawRect(0, 0, width, height);
            graphics.endFill();
            _mask.graphics.beginFill(0x000000);
            _mask.graphics.drawRect(0, 0, width, height);
            _mask.graphics.endFill();
        }

        private final function clickHandler(event:MouseEvent):void
        {
            var pixel:Point = mousePixel;
            var where:OGCPoint = fromPixelToWhere(pixel);

            dispatchEvent(new MapMouseEvent(MapMouseEvent.CLICK, pixel, where, 0, event, event.bubbles, event.cancelable));
        }

        private final function doubleClickHandler(event:MouseEvent):void
        {
            var pixel:Point = mousePixel;
            var where:OGCPoint = fromPixelToWhere(pixel);

            dispatchEvent(new MapMouseEvent(MapMouseEvent.DOUBLE_CLICK, pixel, where, 0, event, event.bubbles, event.cancelable));
        }

        private final function mouseDownHandler(event:MouseEvent):void
        {
            switch (_dragPhase)
            {
                case PHASE_WAIT:
                    _dragPhase = PHASE_START;
                    break;
            }
        }

        private final function mouseMoveHandler(event:MouseEvent):void
        {
            var pixel:Point = mousePixel;
            var where:OGCPoint = fromPixelToWhere(pixel);

            switch (_dragPhase)
            {
                case PHASE_START:
                    dispatchEvent(new MapMouseEvent(MapMouseEvent.DRAG_START, pixel, where, 0, event, event.bubbles, event.cancelable));
                    _dragPhase = PHASE_PROGRESS;
                    break;
                case PHASE_PROGRESS:
                    dispatchEvent(new MapMouseEvent(MapMouseEvent.DRAG_PROGRESS, pixel, where, 0, event, event.bubbles, event.cancelable));
                    break;
            }

            dispatchEvent(new MapMouseEvent(MapMouseEvent.MOUSE_MOVE, pixel, where, 0, event, event.bubbles, event.cancelable));
        }

        private final function get mousePixel():Point
        {
            return new Point(contentMouseX, contentMouseY);
        }

        private final function mouseUpHandler(event:MouseEvent):void
        {
            var pixel:Point = mousePixel;
            var where:OGCPoint = fromPixelToWhere(pixel);

            switch (_dragPhase)
            {
                case PHASE_START:
                    _dragPhase = PHASE_WAIT;
                    break;
                case PHASE_PROGRESS:
                    _dragPhase = PHASE_STOP;
                    dispatchEvent(new MapMouseEvent(MapMouseEvent.DRAG_STOP, pixel, where, 0, event, event.bubbles, event.cancelable));
                    _dragPhase = PHASE_WAIT;
                    break;
            }
        }

        private final function mouseWheelHandler(event:MouseEvent):void
        {
            var pixel:Point = mousePixel;
            var where:OGCPoint = fromPixelToWhere(pixel);

            dispatchEvent(new MapMouseEvent(MapMouseEvent.MOUSE_WHEEL, pixel, where, 0, event, event.bubbles, event.cancelable));
        }

    }
}
